package cn.maihe.elg.operation.centers.epoint.sanming.handler;

import cn.maihe.elg.operation.centers.epoint.sanming.annotation.Decryption;
import cn.maihe.elg.operation.centers.epoint.sanming.auth.EpointAuthorization;
import cn.maihe.elg.operation.centers.epoint.sanming.auth.EpointEncryption;
import cn.maihe.elg.operation.centers.epoint.sanming.config.EpointAuthConfig;
import cn.maihe.elg.operation.centers.epoint.sanming.dto.EpointBaseReqDTO;
import cn.maihe.elg.operation.centers.epoint.sanming.dto.EpointBaseRespDTO;
import cn.maihe.elg.operation.centers.epoint.sanming.enums.EpointRespCodeEnum;
import cn.maihe.elg.operation.centers.epoint.sanming.exception.EpointException;
import cn.maihe.elg.operation.centers.supports.BaseCenterService;
import cn.maihe.elg.operation.centers.supports.CenterTradeContext;
import cn.maihe.elg.operation.model.enums.CenterNoEnum;
import cn.maihe.elg.operation.utils.ElgLogger;
import cn.maihe.elg.operation.utils.ValidateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Map;

/**
 * @Description
 * @Author WJH
 * @Date 2021/04/22
 */
@Slf4j
public abstract class EpointCenterTradeService<REQ extends EpointBaseReqDTO, RESP extends EpointBaseRespDTO> extends BaseCenterService<REQ, RESP> {

    @Resource
    EpointAuthorization authorization;
    @Resource
    EpointEncryption encryption;
    @Resource
    private EpointAuthConfig epointAuthConfig;
    /**
     * 保费费率 默认百分几 ： 三明feeRate默认0.15%，保底300，上不封顶
     */
    private String feeRate;
    private Long minFeeYuan;


    @PostConstruct
    public void init() {
        super.init(this.authorization, this.encryption);
        this.feeRate = this.epointAuthConfig.getFeeRate();
        this.minFeeYuan = this.epointAuthConfig.getMinFeeYuan();
        if (StringUtils.isEmpty(this.feeRate)) {
            this.feeRate = "0.15";
        }
    }

    public String getFeeRate() {
        return feeRate;
    }

    /**
     * @param bidBond 保证金单位分
     * @param feeRate 保费费率默认百分几 0.15即 0.15% 保底300
     * @return 保费 单位分
     */
    public Long calcGuaranteeFee(Long bidBond, String feeRate) {
        if (bidBond == null || StringUtils.isBlank(feeRate)) {
            throw new RuntimeException("保费计算异常:保证金为空或费率为空");
        }
        //0.01% 转0.0001
        BigDecimal rate = new BigDecimal(feeRate).divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP);

        BigDecimal guaranteeFee = new BigDecimal(bidBond).multiply(rate);
        Long guaranteeFeeFen = guaranteeFee.longValue();
        long minFeeFen = this.minFeeYuan * 100;
        if (guaranteeFeeFen < minFeeFen) {
            guaranteeFeeFen = minFeeFen;
        }
        return guaranteeFeeFen;
    }

    public Class<REQ> getReqClass() {
        return reqClass;
    }

    public Class<RESP> getRespClass() {
        return respClass;
    }

    /**
     * 南平新点无整体报文加解密，只有报文中敏感字段加解密
     *
     * @param orgCode
     * @param reqParams
     * @param attrMap   附加属性值
     * @return
     */
    @Override
    public RESP doService(String orgCode, Map<String, Object> reqParams, Map<String, Object> attrMap) {
        ElgLogger.info(log, log -> log.info("[{}:{}][{}] => 原始请求参数:{} 附属属性:{}", centerNoEnum.getCenterNo(), orgCode, tradeService.tradeType(), toJsonString(reqParams), toJsonString(attrMap)));
        CenterNoEnum centerNoEnum = getCenterNoEnum();

        try {
            //验签
            boolean verifyTF = this.verifySignature(reqParams);
            if (!verifyTF) {
                throw EpointException.of(EpointRespCodeEnum.FAILURE, "验签失败");
            }
        } catch (Exception e) {
            throw EpointException.of(EpointRespCodeEnum.FAILURE, "转换失败:" + e.getMessage(), e);
        }

        //反序列化
        REQ enReq = this.buildReq(reqParams);
        //敏感字段解密
        REQ req = this.decryptFiled(enReq);

        ElgLogger.info(log, log -> log.info("解密完请求报文:{}", toJsonString(req)));


        //上下文
        CenterTradeContext context = buildCenterTradeContext(req, centerNoEnum, orgCode, attrMap);

        //参数格式规范校验
        verifyParam(context);

        RESP resp = null;
        try {
            resp = (RESP) processService(context);
            context.setResp(resp);
        } catch (Exception e) {
            if (e instanceof EpointException) {
                throw e;
            }
            throw EpointException.error(e.getMessage(), e);
        }
        // 响应报文无需签名&加密
        return resp;
    }

    private CenterTradeContext buildCenterTradeContext(REQ req, CenterNoEnum centerNoEnum, String orgCode, Map<String, Object> attrMap) {
        CenterTradeContext context = new CenterTradeContext(req, centerNoEnum.getCenterNo(), orgCode, getCurCenterId(centerNoEnum), getOrgId(orgCode));
        context.putAll(attrMap);
        return context;
    }


    protected boolean verifySignature(Map<String, Object> req) {
        try {
            return this.authorization.verifySignature(req, (String) req.get("sign"), (String) req.get("timestamp"));
        } catch (Exception e) {
            throw EpointException.error("验签失败:" + e.getMessage(), e);
        }
    }

    /**
     * 公共 敏感字段解密
     *
     * @return
     */
    protected REQ decryptFiled(REQ req) {
        try {
            Field[] fields = req.getClass().getDeclaredFields();
            Arrays.stream(fields).filter(f -> f.getAnnotation(Decryption.class) != null)
                    .forEach(f -> {
                        f.setAccessible(true);
                        try {
                            String encryptValue = (String) f.get(req);
                            if (StringUtils.isNotBlank(encryptValue)) {
                                String decryptValue = this.encryption.decrypt(encryptValue);
                                f.set(req, decryptValue);
                            }
                        } catch (Exception e) {
                            throw new RuntimeException("[" + f.getName() + "]字段解密异常:" + e.getMessage(), e);
                        }
                        f.setAccessible(false);
                    });
        } catch (Exception e) {
            throw EpointException.of(EpointRespCodeEnum.FAILURE,  e.getMessage(), e);
        }
        return req;
    }

    /**
     * 具体业务处理
     *
     * @param context
     */
    public abstract RESP processService(CenterTradeContext<REQ, RESP> context);

    /**
     * 参数校验：参数规范校验，涉及业务需落库的后置processService处理
     *
     * @param context
     */
    protected void verifyParam(CenterTradeContext<REQ, RESP> context) {
        try {
            ValidateUtil.validate(context.getReq());
        } catch (Exception e) {
            throw EpointException.of(EpointRespCodeEnum.FAILURE, "参数校验失败:" + e.getMessage(), e);
        }
    }

    protected REQ buildReq(Map<String, Object> paramMap) {
        try {
            return objectMapper.readValue(objectMapper.writeValueAsBytes(paramMap), this.reqClass);
        } catch (IOException e) {
            throw EpointException.of(EpointRespCodeEnum.FAILURE, "反序列化异常", e);
        }
    }


}
